package stalker;

import java.io.IOException;
import java.util.Calendar;
import java.util.GregorianCalendar;

import message.Message;
import message.MessageAcceptor;
import network.e2e.FrameReader;
import network.e2e.FrameWriter;
import stalker.room.RoomManager;
import stalker.user.UserManager;
import calendar.CalendarParser;


/**
 * All objects that can stalks!
 * <p>
 * Stalker in server includes both room and user
 * <p>
 * Stalker in clients are identified only by their ID and creation time!
 */

public class Stalker implements MessageAcceptor {
  
  private final static StalkerCatalog catalog_ = new StalkerCatalog();
  
  private volatile static long GLOBAL_STALKER_NEXT_ID = 0L;
  private final static Object GLOBAL_STALKER_NEXT_ID_LOCK = new Object();
  
  public static boolean SERVER = true;
  
  public final static Stalker GUEST_STALKER = Stalker.createStalker("Guest");
  public final static Stalker LOGIN_MANAGER_STALKER = Stalker.createStalker("Login Manager");
  public final static Stalker ROOM_MANAGER_STALKER = RoomManager.create();
  public final static Stalker STALKER_MANAGER = new StalkerManager();
  public final static Stalker USER_MANAGER = new UserManager();
  public final static Stalker DEFAULT = Stalker.createStalker("Default");
  
  private final String name_;
  private final long id_;
  private final Calendar creation_time_;
  
  private void checkRep() {
    assert (creation_time_ != null && name_ != null) :
      "Stalker: fields must not be null";
    assert id_ >= 0 :
      "Stalker: id must be non-negative";
  }
  
  /**
   * Constructs a stalker
   */
  public static Stalker createStalker(String name) {
    assert name != null;
    assert (SERVER);
    Stalker new_stalker =  new Stalker(name);
    return new_stalker;
  }
  
  /**
   * Constructs a Stalker
   */
  protected Stalker(String name) {
    assert name != null;
    assert (SERVER) :
      "Stalker: This is meant to be used on the server side";
    synchronized (GLOBAL_STALKER_NEXT_ID_LOCK) {
      id_ = GLOBAL_STALKER_NEXT_ID;
      ++GLOBAL_STALKER_NEXT_ID;
    }
    creation_time_ = new GregorianCalendar();
    name_ = name;
    catalog_.addNewStalker(this);
    checkRep();
  }
  
  /**
   * Constructs a stalker in client side with a given id and creation time
   */
  public Stalker(long id, Calendar creation_time, String name) {
    assert (!SERVER) :
      "Stalker: This method is meant to be used in client side";
    assert creation_time != null && name != null;
    id_ = id;
    creation_time_ = creation_time;
    name_ = name;
    checkRep();
  }
  
  /**
   * Reads a stalker from the net
   * @requires reader not null
   * @param reader the net
   * @throws IOException 
   */
  private Stalker(FrameReader reader) throws IOException {
    assert reader != null;
    Long uid = reader.readLong();
    assert uid != null;
    id_ = uid;
    creation_time_ = CalendarParser.retrieve(reader);
    assert creation_time_ != null;
    name_ = reader.readString();
    assert name_ != null;
    checkRep();
  }
  
  public static long getStalkerCount() {
    long ret = 0;
    synchronized (GLOBAL_STALKER_NEXT_ID_LOCK) {
      ret = GLOBAL_STALKER_NEXT_ID;
    }
    return ret;
  }
  
  /**
   * Retrieves a stalker from input
   * <p>
   * If in server, retrieves the original Stalker. Otherwise retrieve a replica
   * @throws IOException 
   */
  public static Stalker retrieveStalker(FrameReader reader) throws IOException {
    Stalker coba = new Stalker(reader);
    if (SERVER) {
      return catalog_.getStalker(coba.getID());
    } else {
      return coba;
    }
  }
  
  /**
   * Sends this stalker over the net
   * @requires writer not null
   * @param writer the net
   * @throws IOException 
   */
  public void send(FrameWriter writer) throws IOException {
    assert writer != null;
    writer.writeLong(getID());
    CalendarParser.send(creation_time_, writer);
    writer.writeString(getName());
  }
  
  /**
   * Retrieves the ID of this stalker
   * @return the ID of this stalker
   */
  public long getID() {
    return id_;
  }
  
  /**
   * Retrieves the name of this stalker
   * @return the name of this stalker
   */
  public String getName() {
    return name_;
  }
  
  /**
   * Returns the creation time of this stalker
   * @return the creation time of this stalker
   */
  public Calendar getCreationCalendar() {
    return creation_time_;
  }
  
  /**
   * Accepts a request. Should be overidden
   */
  public void acceptMessage(Message message) {
    //ignore the message
    if (this != GUEST_STALKER) {
      assert false :
        "Stalker:This method is reserved for GUEST_STALKER";
    }
    return;
  }
  
  @Override
  public boolean equals(Object obj) {
    if (!(obj instanceof Stalker)) {
      return false;
    }
    Stalker that = (Stalker)obj;
    return this.getID() == that.getID();
  }
  
  @Override
  public int hashCode() {
    return ((Long)this.getID()).hashCode();
  }
  
  @Override
  public String toString() {
    return this.getName();
  }
  
  /**
   * Retrieves a stalker given its ID
   * <p>
   * If not in server creates a dummy stalker
   * @requires id is positive, there's a stalker with the given ID
   * @param id the ID of the stalker that's being searched
   * @return the Stalker whose ID is <code>id</code>
   */
  public static Stalker getStalker(long id) {
    assert (id > 0L);
    return catalog_.getStalker(id);
  }
  
  /**
   * Checks if an ID is registered
   * @param id id to be checked
   * @return true iff id is a stalker
   */
  public static boolean contains(long id) {
    assert Stalker.SERVER;
    return catalog_.contains(id);
  }
  
}
